package org.yu.team2023.flashdays;


import net.minecraft.ChatFormatting;
import net.minecraft.Util;
import net.minecraft.core.Holder;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.RandomSource;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.damagesource.DamageType;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.levelgen.LegacyRandomSource;
import net.minecraft.world.phys.AABB;
import net.minecraftforge.common.ForgeHooks;

import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;

import static org.yu.team2023.flashdays.ModAttributes.CRIT;
import static org.yu.team2023.flashdays.ModAttributes.CRIT_EFFECT;

public class ModUtil {
    public static final RandomSource SOURCE = new LegacyRandomSource(System.currentTimeMillis());

    public static double rangeRandom(double a, double b) {
        return a + (((b - a) * SOURCE.nextDouble() * 100d) / 100d);
    }

    public static <T> T randomSelect(List<T> list) {
        return list.get(SOURCE.nextInt(list.size()));
    }

    public static boolean isNight(Level level) {
        return level.getDayTime() >= 13000;
    }

    /**
     * 选择一定范围的玩家并执行代码
     *
     * @param source      中心生物
     * @param excludeSelf 不包括自身
     * @param range       范围
     * @param consumer    执行代码
     */
    public static void selectPlayersAndExecute(Player source, boolean excludeSelf, int range, Consumer<ServerPlayer> consumer) {
        if (!(source.level() instanceof ServerLevel serverLevel)) return;
        AABB aabb = AABB.ofSize(source.position(), range, range, range);
        serverLevel.getPlayers((p) -> aabb.contains(p.position())).forEach(serverPlayer -> {
            if (serverPlayer.equals(source) && excludeSelf) return;
            consumer.accept(serverPlayer);
        });
    }

    public static boolean isCrit(Player player) {
        final boolean[] c = {false};
        BSEntityUtil.safeAttribute(player, CRIT, cr -> c[0] = SOURCE.nextDouble() < cr / 100d);
        return c[0];
    }

    public static double getCritEffect(Player player) {
        final double[] c = {0};
        BSEntityUtil.safeAttribute(player, CRIT_EFFECT, cr -> c[0] = cr / 100d);
        return c[0];
    }

    /**
     * 选择一定范围的生物并执行代码
     *
     * @param source      中心生物
     * @param excludeSelf 不包括自身
     * @param range       范围
     * @param consumer    执行代码
     */
    public static void selectEntityAndExecute(Entity source, boolean excludeSelf, int range, Consumer<LivingEntity> consumer) {
        if (!(source.level() instanceof ServerLevel serverLevel)) return;
        AABB aabb = AABB.ofSize(source.position(), range, range, range);
        serverLevel.getEntities(excludeSelf ? source : null, aabb, entity -> entity instanceof LivingEntity && entity.getType() != EntityType.PLAYER)
                .forEach(entity -> {
                    if (entity instanceof LivingEntity living) {
                        consumer.accept(living);
                    }
                });
    }

    /**
     * 判断攻击源是否直接或者间接来自玩家
     */
    public static void runIfPlayerDamage(DamageSource source, BiConsumer<Player, PlayerRPGCapability> consumer) {
        Player player = null;
        if (source.getEntity() instanceof Player p) {
            player = p;
        } else if (source.getDirectEntity() instanceof Player p) {
            player = p;
        }
        if (player != null) {
            Player finalPlayer = player;
            player.getCapability(PlayerRPGCapabilityProvider.CAPABILITY)
                    .ifPresent(playerCuriosSetCapability -> {
                        consumer.accept(finalPlayer, playerCuriosSetCapability);
                    });
        }
    }

    public static void runIfLivingDamage(DamageSource source, Consumer<LivingEntity> consumer) {
        LivingEntity living = null;
        if (source.getEntity() instanceof LivingEntity p) {
            living = p;
        } else if (source.getDirectEntity() instanceof LivingEntity p) {
            living = p;
        }
        if (living != null) {
            consumer.accept(living);
        }
    }

    public static UUID getUUIDSafe(CompoundTag tag, String key) {
        try {
            return tag.getUUID(key);
        } catch (Exception e) {
            return UUID.randomUUID();
        }
    }

    private static final DecimalFormat ATTRIBUTE_MODIFIER_FORMAT = Util.make(new DecimalFormat("#.#"), (p_41704_) -> {
        p_41704_.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.ROOT));
    });

    public static void addModifiersTips(List<Component> list, Attribute attribute, AttributeModifier attributemodifier) {
        double d0 = attributemodifier.getAmount();
        double d1;
        if (attributemodifier.getOperation() != AttributeModifier.Operation.MULTIPLY_BASE && attributemodifier.getOperation() != AttributeModifier.Operation.MULTIPLY_TOTAL) {
            if (attribute.equals(Attributes.KNOCKBACK_RESISTANCE)) {
                d1 = d0 * 10.0D;
            } else {
                d1 = d0;
            }
        } else {
            d1 = d0 * 100.0D;
        }
        if (d0 > 0.0D) {
            list.add(Component.translatable("attribute.modifier.plus." + attributemodifier.getOperation().toValue(), ATTRIBUTE_MODIFIER_FORMAT.format(d1), Component.translatable(attribute.getDescriptionId())).withStyle(ChatFormatting.BLUE));
        } else if (d0 < 0.0D) {
            d1 *= -1.0D;
            list.add(Component.translatable("attribute.modifier.take." + attributemodifier.getOperation().toValue(), ATTRIBUTE_MODIFIER_FORMAT.format(d1), Component.translatable(attribute.getDescriptionId())).withStyle(ChatFormatting.RED));
        }
    }

    public static void findAllPlayersNearby(Consumer<ServerPlayer> serverPlayerConsumer, Entity entity, float distance) {
        if (entity.level() instanceof ServerLevel serverLevel)
            serverLevel.getServer()
                    .getPlayerList().getPlayers()
                    .forEach(serverPlayer -> {
                        if (serverPlayer.distanceTo(entity) < distance)
                            serverPlayerConsumer.accept(serverPlayer);
                    });
    }



    public static final Predicate<Entity> LIVING_NO_PLAYER = entity -> entity instanceof LivingEntity living && living.isAlive() && !(living instanceof Player);
    public static final Predicate<Entity> LIVING = entity -> entity instanceof LivingEntity living && living.isAlive();

    public static double getDamageWithCrit(Player player) {
        if(player.getAttributes().hasAttribute(Attributes.ATTACK_DAMAGE)){
            return player.getAttributeValue(Attributes.ATTACK_DAMAGE);
        }
        return 0;
    }

    public static double getDamageCritEffect(Player player, Entity target) {
        var hit = ForgeHooks.getCriticalHit(player, target, false, 1);
        return hit == null ? 1 : hit.getDamageModifier();
    }

    public static void addAttributeModifier(Player player, Supplier<Attribute> attributeSupplier, String name, double amount, AttributeModifier.Operation operation) {
        if (player.getAttributes().hasAttribute(attributeSupplier.get())) {
            UUID uuid = UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8));
            player.getAttribute(attributeSupplier.get()).addTransientModifier(new AttributeModifier(uuid, name, amount, operation));
        }
    }

    public static void removeAttributeModifier(Player player, Supplier<Attribute> attributeSupplier, String name) {
        if (player.getAttributes().hasAttribute(attributeSupplier.get())) {
            UUID uuid = UUID.nameUUIDFromBytes(name.getBytes(StandardCharsets.UTF_8));
            player.getAttribute(attributeSupplier.get()).removeModifier(uuid);
        }
    }



    public static Holder<DamageType> getDamageTypeUnsafe(ResourceKey<DamageType> key, Level level) {
        return level.registryAccess().registryOrThrow(Registries.DAMAGE_TYPE).getHolderOrThrow(key);
    }


    public static void addMobEffect(LivingEntity entity, MobEffect effect, int tick, int maxLevel) {
        int lv = -1;
        if (entity.hasEffect(effect)) {
            lv = entity.getEffect(effect).getAmplifier();
        }
        entity.addEffect(new MobEffectInstance(effect, tick, Math.min(maxLevel - 1, lv + 1)));
    }


}
